package util

import (
	"encoding/binary"
	"errors"
	"strconv"
)

// number convert utils
var (
	ErrParams = errors.New("params error")
)

func BytesToInt64(n []byte) (int64, error) {
	if n == nil || len(n) < 8 {
		return 0, ErrParams
	}

	return int64(binary.BigEndian.Uint64(n)), nil
}

func BytesToUint16(n []byte) (uint16, error) {
	if n == nil || len(n) < 2 {
		return 0, ErrParams
	}

	return binary.BigEndian.Uint16(n), nil
}

func BytesToInt32(n []byte) (int32, error) {
	if n == nil || len(n) < 4 {
		return 0, ErrParams
	}

	return int32(binary.BigEndian.Uint32(n)), nil
}

func BytesToUint32(n []byte) (uint32, error) {
	if n == nil || len(n) < 2 {
		return 0, ErrParams
	}

	return binary.BigEndian.Uint32(n), nil
}

func BytesToUint64(n []byte) (uint64, error) {
	if n == nil || len(n) < 8 {
		return 0, ErrParams
	}

	return binary.BigEndian.Uint64(n), nil
}

func Int64ToBytes(n int64) ([]byte, error) {
	b := make([]byte, 8)
	binary.BigEndian.PutUint64(b, uint64(n))

	return b, nil
}

func Uint16ToBytes(n uint16) ([]byte, error) {
	b := make([]byte, 2)
	binary.BigEndian.PutUint16(b, n)

	return b, nil
}

func Uint16ToBytes1(dst []byte, n uint16) error {
	if len(dst) < 2 {
		return ErrParams
	}
	binary.BigEndian.PutUint16(dst, n)

	return nil
}

func Uint32ToBytes(n uint32) ([]byte, error) {
	b := make([]byte, 4)
	binary.BigEndian.PutUint32(b, n)

	return b, nil
}

func Uint32ToBytes1(dst []byte, n uint32) error {
	if len(dst) < 4 {
		return ErrParams
	}
	binary.BigEndian.PutUint32(dst, n)

	return nil
}

func Uint64ToBytes(n uint64) ([]byte, error) {
	b := make([]byte, 8)
	binary.BigEndian.PutUint64(b, n)

	return b, nil
}

func Uint64ToBytes1(dst []byte, n uint64) error {
	if len(dst) < 8 {
		return ErrParams
	}
	binary.BigEndian.PutUint64(dst, n)
	return nil
}

func StrBytesToInt64(n []byte) (int64, error) {
	if n == nil {
		return 0, ErrParams
	}
	return strconv.ParseInt(string(n), 10, 64)
}

func StrToInt64(n string) (int64, error) {
	return strconv.ParseInt(n, 10, 64)
}

func StrBytesToUint64(n []byte) (uint64, error) {
	if n == nil {
		return 0, ErrParams
	}
	return strconv.ParseUint(string(n), 10, 64)
}

func StrToUint64(n string) (uint64, error) {
	return strconv.ParseUint(n, 10, 64)
}

func Int64ToStrBytes(n int64) ([]byte, error) {
	return strconv.AppendInt(nil, n, 10), nil
}

//judge whether a string can represent a legal number
type State int
type CharType int

const (
	STATE_INITIAL State = iota
	STATE_INT_SIGN
	STATE_INTEGER
	STATE_POINT
	STATE_POINT_WITHOUT_INT
	STATE_FRACTION
	STATE_EXP
	STATE_EXP_SIGN
	STATE_EXP_NUMBER
	STATE_END
)

const (
	CHAR_NUMBER CharType = iota
	CHAR_EXP
	CHAR_POINT
	CHAR_SIGN
	CHAR_SPACE
	CHAR_ILLEGAL
)

func toCharType(ch byte) CharType {
	switch ch {
	case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
		return CHAR_NUMBER
	case 'e', 'E':
		return CHAR_EXP
	case '.':
		return CHAR_POINT
	case '+', '-':
		return CHAR_SIGN
	case ' ':
		return CHAR_SPACE
	default:
		return CHAR_ILLEGAL
	}
}

func IsNumber(s string) bool {
	transfer := map[State]map[CharType]State{
		STATE_INITIAL: map[CharType]State{
			CHAR_SPACE:  STATE_INITIAL,
			CHAR_NUMBER: STATE_INTEGER,
			CHAR_POINT:  STATE_POINT_WITHOUT_INT,
			CHAR_SIGN:   STATE_INT_SIGN,
		},
		STATE_INT_SIGN: map[CharType]State{
			CHAR_NUMBER: STATE_INTEGER,
			CHAR_POINT:  STATE_POINT_WITHOUT_INT,
		},
		STATE_INTEGER: map[CharType]State{
			CHAR_NUMBER: STATE_INTEGER,
			CHAR_EXP:    STATE_EXP,
			CHAR_POINT:  STATE_POINT,
			CHAR_SPACE:  STATE_END,
		},
		STATE_POINT: map[CharType]State{
			CHAR_NUMBER: STATE_FRACTION,
			CHAR_EXP:    STATE_EXP,
			CHAR_SPACE:  STATE_END,
		},
		STATE_POINT_WITHOUT_INT: map[CharType]State{
			CHAR_NUMBER: STATE_FRACTION,
		},
		STATE_FRACTION: map[CharType]State{
			CHAR_NUMBER: STATE_FRACTION,
			CHAR_EXP:    STATE_EXP,
			CHAR_SPACE:  STATE_END,
		},
		STATE_EXP: map[CharType]State{
			CHAR_NUMBER: STATE_EXP_NUMBER,
			CHAR_SIGN:   STATE_EXP_SIGN,
		},
		STATE_EXP_SIGN: map[CharType]State{
			CHAR_NUMBER: STATE_EXP_NUMBER,
		},
		STATE_EXP_NUMBER: map[CharType]State{
			CHAR_NUMBER: STATE_EXP_NUMBER,
			CHAR_SPACE:  STATE_END,
		},
		STATE_END: map[CharType]State{
			CHAR_SPACE: STATE_END,
		},
	}
	state := STATE_INITIAL
	for i := 0; i < len(s); i++ {
		typ := toCharType(s[i])
		if _, ok := transfer[state][typ]; !ok {
			return false
		} else {
			state = transfer[state][typ]
		}
	}
	return state == STATE_INTEGER || state == STATE_POINT || state == STATE_FRACTION || state == STATE_EXP_NUMBER || state == STATE_END
}
